/* 
 * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
 * Copyright (c) 2006-2018 Christian Walter <cwalter@embedded-solutions.at>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

/* ----------------------- System includes ----------------------------------*/
#include "stdlib.h"
#include "string.h"
#include "stdio.h"

/* ----------------------- Platform includes --------------------------------*/
#include "../../port/port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "../include/mb.h"
#include "../include/mbconfig.h"
#include "../include/mbframe.h"
#include "../include/mbport.h"
#include "mbascii.h"

#if MB_ASCII_ENABLED > 0

/* ----------------------- Defines ------------------------------------------*/
#define MB_ASCII_DEFAULT_CR     '\r'    /*!< Default CR character for Modbus ASCII. */
#define MB_ASCII_DEFAULT_LF     '\n'    /*!< Default LF character for Modbus ASCII. */
#define MB_SER_PDU_SIZE_MIN     3       /*!< Minimum size of a Modbus ASCII frame. */
#define MB_SER_PDU_SIZE_MAX     256     /*!< Maximum size of a Modbus ASCII frame. */
#define MB_SER_PDU_SIZE_LRC     1       /*!< Size of LRC field in PDU. */
#define MB_SER_PDU_ADDR_OFF     0       /*!< Offset of slave address in Ser-PDU. */
#define MB_SER_PDU_PDU_OFF      1       /*!< Offset of Modbus-PDU in Ser-PDU. */

/* ----------------------- Type definitions ---------------------------------*/
typedef enum
{
    BYTE_HIGH_NIBBLE,           /*!< Character for high nibble of byte. */
    BYTE_LOW_NIBBLE             /*!< Character for low nibble of byte. */
} eMBBytePos;

/* ----------------------- Static functions ---------------------------------*/
static uint8_t    prvucMBCHAR2BIN( uint8_t ucCharacter );
static uint8_t    prvucMBBIN2CHAR( uint8_t ucByte );
static uint8_t    prvucMBLRC( uint8_t * pucFrame, uint16_t usLen );

/* ----------------------- Static variables ---------------------------------*/
/* We reuse the Modbus RTU buffer because only one buffer is needed and the
 * RTU buffer is bigger. */
extern uint8_t WHT_RTU_Buffer[MB_SER_PDU_SIZE_MAX];
static uint8_t WHT_ASCII_Buffer[MB_SER_PDU_SIZE_MAX*2+1];

/* ----------------------- Start implementation -----------------------------*/
eMBErrorCode WHT_MB_ASCII_Init(uint8_t ucSlaveAddress)
{
    eMBErrorCode eStatus = MB_ENOERR;

    if(WHT_MB_Port_Serial_Init() != TRUE )
    {
        eStatus = MB_EPORTERR;
    }
    return eStatus;
}
eMBErrorCode WHT_MB_ASCII_Receive(uint8_t* pucRcvAddress, uint8_t** pucFrame, uint16_t* pusLength)
{
    eMBErrorCode eStatus = MB_ENOERR;
    eMBBytePos eBytePos;
    uint16_t ASCII_Length;
    uint16_t usRcvBufferPos;

    /*获取ModBus_ASCII报文*/
    ASCII_Length = WHT_FreeModBus_Scanf(WHT_ASCII_Buffer, sizeof(WHT_ASCII_Buffer));
    /* A new character is received. If the character is a ':' the input
     * buffer is cleared. A CR-character signals the end of the data
     * block. Other characters are part of the data block and their
     * ASCII value is converted back to a binary representation.
     */
    if (WHT_ASCII_Buffer[0] == ':')
    {
        eBytePos = BYTE_HIGH_NIBBLE;
        ASCII_Length -= 1;
        usRcvBufferPos = 0;
    }
    else
        return MB_EIO;
    for (uint16_t i = 0; i < ASCII_Length; i++)
    {
        static volatile uint8_t* ASICC_Buffer = &WHT_ASCII_Buffer[1];

        if (ASICC_Buffer[i] == MB_ASCII_DEFAULT_CR)// MB_ASCII_DEFAULT_LF
            break;
        /* High nibble of the byte comes first. We check for a buffer overflow here. */
        if (eBytePos == BYTE_HIGH_NIBBLE)
        {
            eBytePos = BYTE_LOW_NIBBLE;
            WHT_RTU_Buffer[i/2] = prvucMBCHAR2BIN(ASICC_Buffer[i]) << 4;
        }
        else if (eBytePos == BYTE_LOW_NIBBLE)
        {
            eBytePos = BYTE_HIGH_NIBBLE;
            WHT_RTU_Buffer[i/2] |= prvucMBCHAR2BIN(ASICC_Buffer[i]);
            usRcvBufferPos++;
        }
    }

    assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );
    /* Length and CRC check */
    if ((usRcvBufferPos >= MB_SER_PDU_SIZE_MIN) && (prvucMBLRC(WHT_RTU_Buffer, usRcvBufferPos) == 0 ))
    {
        /* Save the address field. All frames are passed to the upper layed and the decision if a frame is used is done there.*/
        *pucRcvAddress = WHT_RTU_Buffer[MB_SER_PDU_ADDR_OFF];

        /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus  size of address field and CRC checksum.*/
        *pusLength = (uint16_t)( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC );

        /* Return the start of the Modbus PDU to the caller. */
        *pucFrame = (uint8_t*)&WHT_RTU_Buffer[MB_SER_PDU_PDU_OFF];
    }
    else
    {
        printf("MB ASCII LRC ERROR!!!\r\n");
        printf("LRC=0x%02x\r\n", prvucMBLRC(WHT_RTU_Buffer, usRcvBufferPos - MB_SER_PDU_SIZE_LRC));
        eStatus = MB_EIO;
    }
    return eStatus;
}
eMBErrorCode WHT_MB_ASCII_Send(uint8_t ucSlaveAddress, const uint8_t* pucFrame, uint16_t usLength)
{
    static uint8_t *pucSndBufferCur;
    static uint16_t usSndBufferCount;
    eMBBytePos eBytePos;

    /* First byte before the Modbus-PDU is the slave address. */
    pucSndBufferCur = ( uint8_t * ) pucFrame - 1;
    usSndBufferCount = 1;

    /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
    pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
    usSndBufferCount += usLength;

    /* Calculate LRC checksum for Modbus-Serial-Line-PDU. */
    WHT_ASCII_Buffer[usSndBufferCount++] = prvucMBLRC((uint8_t*)pucSndBufferCur, usSndBufferCount);

    /* Start of transmission. The start of a frame is defined by sending the character ':'. */
    WHT_ASCII_Buffer[0] = ':';
    eBytePos = BYTE_HIGH_NIBBLE;
    /* Send the data block. Each data byte is encoded as a character hex
     * stream with the high nibble sent first and the low nibble sent
     * last. If all data bytes are exhausted we send a '\r' character
     * to end the transmission. */
    usSndBufferCount = (usSndBufferCount -1) * 2;
    uint8_t i;
    for (i = 1; i <= usSndBufferCount; i++)
    {
        if (eBytePos == BYTE_HIGH_NIBBLE)
        {
            eBytePos = BYTE_LOW_NIBBLE;
            WHT_ASCII_Buffer[i] = prvucMBBIN2CHAR((uint8_t)(*pucSndBufferCur >> 4));
        }
        else if (eBytePos == BYTE_LOW_NIBBLE)
        {
            eBytePos = BYTE_HIGH_NIBBLE;
            WHT_ASCII_Buffer[i] = prvucMBBIN2CHAR((uint8_t)(*pucSndBufferCur & 0x0F));
            pucSndBufferCur++;
        }
    }
    WHT_ASCII_Buffer[i++] = MB_ASCII_DEFAULT_CR;
    WHT_ASCII_Buffer[i++] = MB_ASCII_DEFAULT_LF;
    /* Activate the transmitter. */
    WHT_FreeModBus_Printf(WHT_ASCII_Buffer, usSndBufferCount + 1 + 2);// : \r \n
    return MB_ENOERR;
}

static uint8_t prvucMBCHAR2BIN(uint8_t ucCharacter)
{
    if( ( ucCharacter >= '0' ) && ( ucCharacter <= '9' ) )
    {
        return ( uint8_t )( ucCharacter - '0' );
    }
    else if( ( ucCharacter >= 'A' ) && ( ucCharacter <= 'F' ) )
    {
        return ( uint8_t )( ucCharacter - 'A' + 0x0A );
    }
    else
    {
        return 0xFF;
    }
}
static uint8_t prvucMBBIN2CHAR(uint8_t ucByte)
{
    if( ucByte <= 0x09 )
    {
        return ( uint8_t )( '0' + ucByte );
    }
    else if( ( ucByte >= 0x0A ) && ( ucByte <= 0x0F ) )
    {
        return ( uint8_t )( ucByte - 0x0A + 'A' );
    }
    else
    {
        /* Programming error. */
        assert( 0 );
    }
    return '0';
}
static uint8_t prvucMBLRC(uint8_t* pucFrame, uint16_t usLen)
{
    uint8_t ucLRC = 0;  /* LRC char initialized */

    while( usLen-- )
    {
        ucLRC += *pucFrame++;   /* Add buffer byte without carry */
    }
    /* Return twos complement */
    ucLRC = ( uint8_t ) ( -( ( char ) ucLRC ) );
    return ucLRC;
}

#endif
